package com.trz.cqrs.core.async;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.trz.cqrs.core.entity.EventEntity;
import com.trz.cqrs.core.enums.EventState;
import com.trz.cqrs.es.event.Event;
import com.trz.cqrs.es.event.EventView;
import com.trz.cqrs.core.repository.EventRepository;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Optional;

import static com.google.common.base.Preconditions.checkNotNull;

@Slf4j
@Component
class RetryProcessor {

    @Resource
    private EventRepository repository;

    @Resource
    private ObjectMapper objectMapper;

    private final LoadingCache<String, Class<Event>> cache = CacheBuilder.newBuilder().build(new CacheLoader<String, Class<Event>>() {
        @Override
        public Class<Event> load(final String eventType) throws Exception {
            return (Class<Event>) Class.forName(eventType);
        }
    });

    @Retryable
    Event lock(EventView view) {
        checkNotNull(view);
        checkNotNull(view.getId());
        EventEntity entity = repository.updateProcessAndGet(view);
        return toEvent(Optional.of(entity).get());
    }

    private Event toEvent(EventEntity entity) {
        try {
            val clazz = cache.getUnchecked(entity.getEventType());
            val event = objectMapper.readValue(entity.getPayload(), clazz);


            event.setId(entity.getId());
            event.setCount(entity.getCount());
            event.setCategory(entity.getCategory());
            event.setIdentifier(entity.getIdentifier());
            return event;
        } catch (final Exception e) {
            /* mark as failed & fill in error if needed */

            EventEntity eventEntity = repository.getById(entity.getId());

            Optional.ofNullable(eventEntity).ifPresent(x -> {
                x.setEventState(EventState.FAILED);
                x.onError(e);
                repository.save(x);
            });
            log.error("Error caught in processing event {} => exception: '{}' message: '{}'", entity.getId(), e.getClass().getSimpleName(), e.getMessage());
        }
        return null;
    }

}
